/*
 * Copyright 2013-2019 Xia Jun(3979434@qq.com).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ***************************************************************************************
 *                                                                                     *
 *                        Website : http://www.farsunset.com                           *
 *                                                                                     *
 ***************************************************************************************
 */
package com.farsunset.cim.sdk.server.slc.handler.tcp;

import com.farsunset.cim.acceptor.config.WebsocketConfig;
import com.farsunset.cim.coder.json.TextMessageDecoder;
import com.farsunset.cim.coder.json.TextMessageEncoder;
import com.farsunset.cim.coder.protobuf.WebMessageDecoder;
import com.farsunset.cim.coder.protobuf.WebMessageEncoder;
import com.farsunset.cim.constant.WebsocketProtocol;
import com.farsunset.cim.sdk.server.slc.handler.SlcAcceptorBuilder;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;

import java.util.Map;
import java.util.concurrent.TimeUnit;

@Sharable
public class WebsocketAcceptorPlus extends WebsocketAcceptor2 {

    protected final boolean loadDeHandler;
    protected final boolean usePlatformOptimization;
    protected final Map<String, ChannelHandler> appChannelHandlerMapByDeBefore;
    protected final Map<String, ChannelHandler> appChannelHandlerMapByDeAfter;
    protected final Map<String, ChannelHandler> webChannelHandlerMapByDeBefore;
    protected final Map<String, ChannelHandler> webChannelHandlerMapByDeAfter;

    public WebsocketAcceptorPlus(SlcBuilder builder) {
        super(builder.config, builder.isUsePlatformOptimization());
        this.loadDeHandler = builder.isLoadDeHandler();
        this.usePlatformOptimization = builder.isUsePlatformOptimization();
        this.appChannelHandlerMapByDeBefore = builder.getAppChannelHandlerMapByDeBefore();
        this.appChannelHandlerMapByDeAfter = builder.getAppChannelHandlerMapByDeAfter();
        this.webChannelHandlerMapByDeBefore = builder.getWebChannelHandlerMapByDeBefore();
        this.webChannelHandlerMapByDeAfter = builder.getWebChannelHandlerMapByDeAfter();
    }

    @Override
    public void bind() {

        if (!config.isEnable()) {
            return;
        }

        ServerBootstrap bootstrap = createServerBootstrap();
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {

            @Override
            public void initChannel(SocketChannel ch) {
                ch.pipeline().addLast(blacklistHandler);
                ch.pipeline().addLast(new HttpServerCodec());
                ch.pipeline().addLast(new ChunkedWriteHandler());
                ch.pipeline().addLast(new HttpObjectAggregator(4 * 1024));
                ch.pipeline().addLast(authHandler);
                ch.pipeline().addLast(new WebSocketServerProtocolHandler(config.getPath(), true));
                for (Map.Entry<String, ChannelHandler> entry : webChannelHandlerMapByDeBefore.entrySet()) {
                    ch.pipeline().addLast(entry.getKey(), entry.getValue());
                }

                if (loadDeHandler) {
                    if (config.getProtocol() == WebsocketProtocol.JSON) {
                        ch.pipeline().addLast(new TextMessageDecoder());
                        ch.pipeline().addLast(new TextMessageEncoder());
                    } else {
                        ch.pipeline().addLast(new WebMessageDecoder());
                        ch.pipeline().addLast(new WebMessageEncoder());
                    }
                }
                for (Map.Entry<String, ChannelHandler> entry : webChannelHandlerMapByDeAfter.entrySet()) {
                    ch.pipeline().addLast(entry.getKey(), entry.getValue());
                }
                ch.pipeline().addLast(new IdleStateHandler(config.getReadIdle().getSeconds(), config.getWriteIdle().getSeconds(), 0, TimeUnit.SECONDS));
                ch.pipeline().addLast(socketConfig.getLoggingHandler() == null ? defaultLoggingHandler : socketConfig.getLoggingHandler());
                ch.pipeline().addLast(WebsocketAcceptorPlus.this);
                ch.pipeline().addLast(illegalRequestHandler);
            }

        });

        ChannelFuture channelFuture = bootstrap.bind(config.getPort()).syncUninterruptibly();
        channelFuture.channel().newSucceededFuture().addListener(future -> {
            if (config.getProtocol() == WebsocketProtocol.JSON) {
                logger.info(JSON_BANNER, config.getPort());
            }
            if (config.getProtocol() == WebsocketProtocol.PROTOBUF) {
                logger.info(PROTOBUF_BANNER, config.getPort());
            }
        });
        channelFuture.channel().closeFuture().addListener(future -> this.destroy());
    }

    public static class SlcBuilder extends SlcAcceptorBuilder<WebsocketAcceptorPlus> {
        private WebsocketConfig config;

        public SlcBuilder setConfig(WebsocketConfig config) {
            this.config = config;
            return this;
        }

        public WebsocketAcceptorPlus build() {
            return new WebsocketAcceptorPlus(this);
        }

    }
}